package aryn.framework.reflection;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class ClassUtil {

	public static Field[] getAllFields(Class<?> clazz) {
		ArrayList<Field> fieldsList = new ArrayList<Field>();
		Class<?> temp = clazz;

		while (!temp.getName().equals("java.lang.Object")) {
			if (temp.getDeclaredFields() != null)
				fieldsList.addAll(Arrays.asList(temp.getDeclaredFields()));
			temp = temp.getSuperclass();
		}
		return fieldsList.toArray(new Field[0]);
	}

	public static Field[] getFieldsNull(Object obj) {
		Class<?> objClass = obj.getClass();
		Field[] fields = getAllFields(objClass);
		ArrayList<Field> fieldsList = new ArrayList<Field>();
		try {
			for (Field f : fields) {
				f.setAccessible(true);
				if (f.get(obj) == null)
					fieldsList.add(f);
			}
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return fieldsList.toArray(new Field[0]);
	}

	public static String[] getFieldsName(Field[] fields) {
		ArrayList<String> fieldsList = new ArrayList<String>();
		for (Field f : fields)
			fieldsList.add(f.getName());
		return fieldsList.toArray(new String[0]);
	}

	public static Method[] getAllMethods(Class<?> clazz) {
		ArrayList<Method> methodsList = new ArrayList<Method>();
		Class<?> temp = clazz;

		while (!temp.getName().equals("java.lang.Object")) {
			if (temp.getDeclaredFields() != null)
				methodsList.addAll(Arrays.asList(temp.getDeclaredMethods()));
			temp = temp.getSuperclass();
		}
		return methodsList.toArray(new Method[0]);
	}

	public static String[] getMethodsName(Method[] methods) {
		ArrayList<String> methodsList = new ArrayList<String>();
		for (Method f : methods)
			methodsList.add(f.getName());
		return methodsList.toArray(new String[0]);
	}

	public static Object trasform(Object objTo, Object objFrom)
			throws InstantiationException, IllegalAccessException,
			MappingAnnotationExecption, InvocationTargetException {
		if (objFrom == null)
			return null;

		Class<?> clsTo = objTo.getClass();
		Class<?> clsFrom = objFrom.getClass();
		Method[] toMethods = clsTo.getMethods();
		Method[] fromMethos = clsFrom.getMethods();

		ArrayList<Field> fieldsList = new ArrayList<Field>();
		Class<?> temp = clsTo;

		while (!temp.getName().equals("java.lang.Object")) {
			if (temp.getDeclaredFields() != null) {
				fieldsList.addAll(Arrays.asList(temp.getDeclaredFields()));
			}
			temp = temp.getSuperclass();
		}
		Field[] fields = fieldsList.toArray(new Field[fieldsList.size()]);

		for (int i = 0; i < fields.length; i++) {
			Field field = fields[i];
			// System.out.println("fields : " + field.getName() +
			// " - className " + field.getType().getName());

			String setMethodName = getSetterName(field.getName());
			String getMethodName = getGetterName(field);
			if ((methodExists(toMethods, setMethodName))
					&& (methodExists(fromMethos, getMethodName))) {
				Class<?>[] setMethodArrayParam = { field.getType() };
				Class<?>[] getMethodArrayParam = {};
				Object[] noParamInvokeMethod = {};
				Method setMethod;
				Method getMethod;
				try {
					setMethod = clsTo.getMethod(setMethodName,
							setMethodArrayParam);
					getMethod = clsFrom.getMethod(getMethodName,
							getMethodArrayParam);
					Object objToSet = getMethod.invoke(objFrom,
							noParamInvokeMethod);
					if (!(objToSet == null)) {
						// controllo se il metodo si aspetta lo stesso oggetto
						// come parametro
						if (!MethodsWithSameParameters(toMethods,
								setMethodName, objToSet)) {
							// effettuo la trasformazioni tra i 2 tipi di
							// oggetti
							// raffy
							// Class cls =
							// getParameterMethodClass(setMethodName,
							// toMethods);
							Class<?> cls = field.getType();
							if (cls != null) {
								objToSet = trasform(cls, objToSet);
							}
						} else {
							// anche se sono dello stesso tipo, devo gestire le
							// collezioni.
							if (Collection.class.isInstance(objToSet)) {
								if (field
										.isAnnotationPresent(MappedClass.class)) {
									MappedClass mapCls = field
											.getAnnotation(MappedClass.class);
									Class<?> clToSet = mapCls.mCls();
									objToSet = trasform(clToSet,
											(Collection<?>) objToSet);
								} else {
									throw new MappingAnnotationExecption(
											"Trasformation from "
													+ clsFrom.getName()
													+ " to "
													+ clsTo.getName()
													+ "Collection Parameter "
													+ field.getName()
													+ " not have a MappedClass annotation set");
								}
							}
						}

						setMethod.invoke(objTo, objToSet);
						// System.out.println("setMethod : " +
						// setMethod.getName() + " getMethod : " +
						// getMethod.getName());
					}
				} catch (SecurityException ex) {
					throw new MappingAnnotationExecption(ex);
				} catch (NoSuchMethodException ex) {
					throw new MappingAnnotationExecption(ex);
				} catch (NullPointerException npe) {
					npe.printStackTrace();
				}
			} else { // gestione caso in cui non e' stata trovata una
						// corrispondenza tra i nomi dei metodi set e get
				// controllo se e' stata effettuata la mappatura tramite
				// l'annotazione MappedField
				if (field.isAnnotationPresent(MappedField.class)) {
					MappedField mappedField = field
							.getAnnotation(MappedField.class);
					Class<?>[] setMethodArrayParam = { field.getType() };
					Class<?>[] getMethodArrayParam = {};
					Object[] noParamInvokeMethod = {};
					Method setMethod;

					try {
						setMethod = clsTo.getMethod(setMethodName,
								setMethodArrayParam);
						String strMappedField = (String) mappedField.field();
						Object objFromMapped = objFrom;
						Class<?> clsFromMapped = objFromMapped.getClass();
						if (strMappedField != null) {
							if (!strMappedField.equals("")) {
								String[] arrayMappedField = strMappedField
										.split("[.]");
								for (int j = 0; j < arrayMappedField.length; j++) {
									String mappedFieldName = arrayMappedField[j];
									// Field getterField =
									// clsFromMapped.getDeclaredField(mappedFieldName);
									// String mappedGetterName =
									// getGetterName(getterField);
									String mappedGetterName = "get"
											+ javaMethodNameField(mappedFieldName);
									Method getMethod = clsFromMapped.getMethod(
											mappedGetterName,
											getMethodArrayParam);
									objFromMapped = getMethod.invoke(
											objFromMapped, noParamInvokeMethod);
									clsFromMapped = objFromMapped.getClass();
								}
								// dopo aver calcolato tutta la ricorsione degli
								// oggetti setto il parametro
								if (objFromMapped != null) {
									setMethod.invoke(objTo, objFromMapped);
								}
							}
						}
					} catch (SecurityException ex) {
						throw new MappingAnnotationExecption(ex);
					} catch (NoSuchMethodException ex) {
						throw new MappingAnnotationExecption(ex);
					}
				}

			}
		}
		return objTo;
	}

	private static String getSetterName(String fieldName) {
		String strSetterName = "set" + javaMethodNameField(fieldName);
		return strSetterName;
	}

	private static String getGetterName(Field fieldName) {
		String strGetterName;
		boolean booleanType = "boolean".equals(fieldName.getType().getName());
		if (!booleanType) {
			strGetterName = "get" + javaMethodNameField(fieldName.getName());
		} else {
			strGetterName = "is" + javaMethodNameField(fieldName.getName());
		}
		return strGetterName;
	}

	private static String javaMethodNameField(String string) {
		String javaCase = string.substring(0, 1).toUpperCase()
				+ string.substring(1, string.length());
		return javaCase;
	}

	private static boolean methodExists(Method[] methods, String methodName) {
		boolean retValue = false;
		for (int i = 0; i < methods.length; i++) {
			Method method = methods[i];
			if (method.getName().equals(methodName)) {
				retValue = true;
				break;
			}
		}
		return retValue;
	}

	private static boolean MethodsWithSameParameters(Method[] arrayMethods,
			String setMethodName, Object getObject)
			throws MappingAnnotationExecption {
		boolean retValue = false;
		boolean methodFound = false;
		for (int i = 0; i < arrayMethods.length; i++) {
			Method method = arrayMethods[i];
			if (method.getName().equals(setMethodName)) {
				methodFound = true;
				Class<?> param = method.getParameterTypes()[0];
				if (param.isPrimitive() || param.isInterface()) {
					retValue = true;
					break;
				}

				if (param.isInstance(getObject)) {
					retValue = true;
					break;
				}
			}
		}
		if (!methodFound) {
			throw new MappingAnnotationExecption(
					"MethodsWithSameParameters - Method : " + setMethodName
							+ " in class not found!");
		}
		return retValue;
	}
}
